unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, FireDAC.Stan.Intf, FireDAC.Stan.Option,
  FireDAC.Stan.Error, FireDAC.UI.Intf, FireDAC.Phys.Intf, FireDAC.Stan.Def,
  FireDAC.Stan.Pool, FireDAC.Stan.Async, FireDAC.Phys, Data.DB, FireDAC.Phys.Oracle,
  FireDAC.Comp.Client, FireDAC.Phys.OracleDef, FireDAC.Stan.Param, FireDAC.DatS,
  FireDAC.DApt.Intf, FireDAC.DApt, FireDAC.Comp.DataSet, Vcl.Grids, Vcl.DBGrids,
  Vcl.StdCtrls, FireDAC.VCLUI.Wait, FireDAC.Comp.UI, Vcl.DBCtrls, Vcl.OleAuto,
  System.DateUtils, System.Types, System.Generics.Collections, System.Generics.Defaults,
  System.Math, System.IOUtils, Unit4;

type
  TForm1 = class(TForm)
    FDConnection1: TFDConnection;
    FDQuery1: TFDQuery;
    DataSource1: TDataSource;
    DBGrid1: TDBGrid;
    Button1: TButton;
    FDGUIxWaitCursor1: TFDGUIxWaitCursor;
    EditQuery: TEdit;
    Button2: TButton;
    procedure DoRevise;
    procedure TheEnd ( t : string );
    procedure FindTerminal;
    procedure ReadTerm;
    procedure GetLogs;
    procedure CleanUpLogs;
    function  DBGetTransactionItem: Boolean;
    function  DBGetShift: Boolean;
    function  DBGetMoneyInfo: Boolean;
    procedure GetESPP ( alsoGetAction: Boolean );
    procedure GetOFD ( doSomething: Boolean );
    procedure GetUpAndRunning ( s: string );
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

   TConfig = record
      ofdPeriod, dt: integer; {период сверки в сутках с текущего момента}
      esppLogin, ofdLogin, ofdPassword, termFile, strRunOfd, strRun7z: string;
      keepGlobalLog: Boolean;
   end;

   TTerm = record
      SN, IP, ofdN: string;
   end;

   TTransactionItemRecord = record
      actId,
      svcNum
         : string;
   end;

   TShiftRecord = record
      actId,
      data
         : string;
      hasSvcNum
         : boolean;
   end;

   TDecodedDate = record
      Year, Month, Day: Word;
   end;

var
   dir: TDirectory;
   emptyFiles: TStringDynArray;
   Form1: TForm1;
   Config: TConfig;
   Term: TList<TTerm>;
   TermNum: Integer;
   s, ts, st: TStringList;
   grepFiles: TStringList;

implementation

{$R *.dfm}

function grepString ( spanInWholeDays: integer ) : string;
var
   s, t, res:
      string;
   i, j, k:
      Word;
   dateNow:
      TDecodedDate;
   dOld,
   dNew,
   dNow:
      TDateTime;
   found:
      Boolean;

begin
   grepFiles.Clear;

   dNew := Today;
   dOld := IncDay ( dNew , 1 - spanInWholeDays );

   {заполнение списка}
   dNow := dOld;
   repeat
      with dateNow do  DecodeDate ( dNow, Year , Month , Day );
      with dateNow do  t := ' kiosk.log.' + FormatFloat ( '0000' , Year ) + '-' + FormatFloat ( '00' , Month ) + '-' + FormatFloat ( '00' , Day );
      grepFiles.Add( t );
      dNow := IncDay ( dNow , 1 );
   until CompareDateTime ( dNow , dNew ) >= 0;

   {удаление десятки дней}
   repeat
      k := 0;
      s := '';
      t := '';
      found := False;
      for i := 0 to grepFiles.Count - 1 do  begin

         if grepFiles [ i ].EndsWith( '0' ) or grepFiles [ i ].EndsWith( '01' )
         then begin
            if grepFiles [ i ].EndsWith( '0' )
            then begin
               k := 10;
               t := '9';
            end
            else
               if grepFiles [ i ].EndsWith( '01' )
               then begin
                  k := 9;
                  t := '9';
               end;
            s := grepFiles [ i ].Substring ( 0 , grepFiles [ i ].Length - 1 );
            for j := i + 1 to grepFiles.Count - 1 do
               if grepFiles [ j ] = s + '9'
               then begin
                  found := True;
                  Break;
               end;
            if found
            then begin
               for j := 1 to k do  begin
                  grepFiles.Delete( i );
               end;
               grepFiles.Add( s + '*' );
               Break;
            end;

         end;

      end;
   until not found;

   grepFiles.Sort;
   res := '';
   for i := 0 to grepFiles.Count - 1 do
      res := res + grepFiles [ i ];
   grepFiles.Clear;
   res := res + ' kiosk.log';

   grepString := res;

end;

procedure TForm1.TheEnd (t: string);
begin
   s.Clear;
   if t <> ''
   then begin
{      s := TStringList.Create;}
      s.Add( t );
      if s.Count > 0 then s.SaveToFile('nothing to revise.txt');
   end;
   Application.Terminate;
end;

function TForm1.DBGetTransactionItem: Boolean;
var
   t : string;
   i : Integer;
{   tiRecord: TTransactionItemRecord;}
{   wordie : TArray<string>; {Слова строки}
begin
   if ( TermNum < 0 ) or ( TermNum > Term.Count - 1 )
   then begin
      DBGetTransactionItem := False;
      Exit;
   end;

   try

      FDQuery1.Active := False;
      FDQuery1.SQL.Clear;
      DBGrid1.Columns.Clear;

      FDQuery1.SQL.Add('select e.act_id,f.tri_svc_num from t_shift a,t_kkm_is_org b,td_terminal d,t_terminal_kkm c,t_action e,');
      FDQuery1.SQL.Add('t_transaction_item f where f.tri_tr_id in e.act_tr_id and e.act_sh_id in a.sh_id and a.sh_kio_id in b.kio_id and');
      FDQuery1.SQL.Add('b.kio_kkm_id in c.tk_kkm_id and c.tk_terminal_id in d.ter_id and c.tk_date_end IS NULL and d.ter_serial_number =');
      FDQuery1.SQL.Add('''' + Term [ TermNum ].SN + '''');

      WriteToGlobalLog ('Начат запрос SvcNum по ActId');
      FDQuery1.Active := True;

      DBGrid1.DataSource.DataSet.DisableControls;
      DBGrid1.DataSource.DataSet.First;
      WriteToGlobalLog (     'Завершён запрос SvcNum по ActId');
      s.Clear;
      while not DBGrid1.DataSource.DataSet.Eof do begin
         t := ''; // Пропускаем невидимые столбцы и всякие там мемо
         for i := 0 to Pred(DBGrid1.Columns.Count) do
            if DBGrid1.Columns[i].Visible and not ( DBGrid1.Columns[i].Field.DataType in ftNonTextTypes )
            then t := t + '"'+DBGrid1.DataSource.DataSet.FieldByName(DBGrid1.Columns[i].FieldName).AsString+'";';
         s.Add ( t );

         DBGrid1.DataSource.DataSet.Next;

      end;
      s.SaveToFile('action1.csv');
      s.Clear;
      DBGrid1.DataSource.DataSet.EnableControls;
      WriteToGlobalLog ('Сохранён результат запроса SvcNum по ActId');
      DBGetTransactionItem := True;
   except
      WriteToGlobalLog ('Не удалось получить SvcNum по ActId');
      DBGetTransactionItem := False;
   end;
end;

function TForm1.DBGetShift: Boolean;
var
   t : string;
   i : Integer;
{   shRecord: TShiftRecord;}
{   wordie : TArray<string>; {Слова строки}
begin
   if ( TermNum < 0 ) or ( TermNum > Term.Count - 1 )
   then begin
      DBGetShift := False;
      Exit;
   end;

   try
      FDQuery1.Active := False;
      FDQuery1.SQL.Clear;
      DBGrid1.Columns.Clear;

      FDQuery1.SQL.Add('select e.act_id,e.act_tr_id,e.act_at_id,e.act_pt_id,e.act_acc_id,to_char(e.act_dt),e.act_amount from t_shift a,');
      FDQuery1.SQL.Add('t_kkm_is_org b,td_terminal d,t_terminal_kkm c,t_action e where e.act_sh_id in a.sh_id and a.sh_kio_id in b.kio_id');
      FDQuery1.SQL.Add('and b.kio_kkm_id in c.tk_kkm_id and c.tk_terminal_id in d.ter_id and c.tk_date_end IS NULL and d.ter_serial_number =');
      FDQuery1.SQL.Add('''' + Term [ TermNum ].SN + '''');

      WriteToGlobalLog ('Начат запрос полей T_ACTION');
      FDQuery1.Active := True;

      DBGrid1.DataSource.DataSet.DisableControls;
      DBGrid1.DataSource.DataSet.First;
      WriteToGlobalLog (     'Завершён запрос полей T_ACTION' );
      s.Clear;
      while not DBGrid1.DataSource.DataSet.Eof do begin
         t := ''; // Пропускаем невидимые столбцы и всякие там мемо
         for i := 0 to Pred(DBGrid1.Columns.Count) do
            if DBGrid1.Columns[i].Visible and not ( DBGrid1.Columns[i].Field.DataType in ftNonTextTypes )
            then t := t + '"'+DBGrid1.DataSource.DataSet.FieldByName(DBGrid1.Columns[i].FieldName).AsString+'";';
         s.Add( t );

         DBGrid1.DataSource.DataSet.Next;

      end;
      s.SaveToFile('action2.csv');
      s.Clear;
      DBGrid1.DataSource.DataSet.EnableControls;
      WriteToGlobalLog ('Сохранён результат запроса полей T_ACTION');
      DBGetShift := True;
   except
      WriteToGlobalLog ('Не удалось получить данные T_ACTION');
      DBGetShift := False;
   end;
end;

function TForm1.DBGetMoneyInfo: Boolean;
var
   t : string;
   i : Integer;
begin
   if ( TermNum < 0 ) or ( TermNum > Term.Count - 1 )
   then begin
      DBGetMoneyInfo := False;
      Exit;
   end;

   try
      FDQuery1.Active := False;
      FDQuery1.SQL.Clear;
      DBGrid1.Columns.Clear;

      FDQuery1.SQL.Add('select s.sh_id, to_char(s.sh_from_dt), to_char(s.sh_to_dt), a.act_id, to_char(a.act_dt), a.act_at_id, a.act_amount, a.act_comment,');
      FDQuery1.SQL.Add('a.act_tr_id, a.act_pt_id, a.act_acc_id, m.mi_denomination, m.mi_cnt from t_shift s, t_action a, t_money_info m where s.sh_kio_id in');
      FDQuery1.SQL.Add('( select k.kio_id from t_kkm_is_org k where k.kio_kkm_id in (select tk.tk_kkm_id  from TD_TERMINAL t, T_TERMINAL_KKM tk');
      FDQuery1.SQL.Add('where t.ter_serial_number = ''' + Term [ TermNum ].SN + ''' and tk.TK_TERMINAL_ID = t.TER_ID and tk.TK_DATE_END IS NULL ) )');
      FDQuery1.SQL.Add('and s.sh_id = a.act_sh_id and a.act_id = m.mi_act_id(+)');

      WriteToGlobalLog ('Начат запрос смен, транзакций и купюр (shifts.csv)');
      FDQuery1.Active := True;

      DBGrid1.DataSource.DataSet.DisableControls;
      s.Clear;
      DBGrid1.DataSource.DataSet.First;
      WriteToGlobalLog ('Завершён запрос смен, транзакций и купюр (shifts.csv)');
      while not DBGrid1.DataSource.DataSet.Eof do begin
         t := ''; // Пропускаем невидимые столбцы и всякие там мемо
         for i := 0 to Pred(DBGrid1.Columns.Count) do
            if DBGrid1.Columns[i].Visible and not ( DBGrid1.Columns[i].Field.DataType in ftNonTextTypes )
            then t := t + '"'+DBGrid1.DataSource.DataSet.FieldByName(DBGrid1.Columns[i].FieldName).AsString+'";';
         s.Add(t);
         DBGrid1.DataSource.DataSet.Next
      end;
      DBGrid1.DataSource.DataSet.EnableControls;
      if s.Count > 0 then s.SaveToFile('shifts.csv');
      s.Clear;
      WriteToGlobalLog ('Сохранён файл shifts.csv');
      DBGetMoneyInfo := True;
   except
      DBGetMoneyInfo := False;
      WriteToGlobalLog ('Не удалось получить данные T_MONEY_INFO');
   end;
end;

procedure TForm1.GetESPP ( alsoGetAction: Boolean );
var
   i : Byte;
   a , b , c : Boolean;
begin
   a := not alsoGetAction;
   b := a;
   c := False;
   for i := 0 to 20 do  begin
      if not a then a := DBGetTransactionItem;
      if not b then b := DBGetShift;
      if not c then c := DBGetMoneyInfo;
      if a and b and c then Break;
   end;
end;

procedure TForm1.FindTerminal;
var
   Found: Boolean;
   i: integer;
begin
   TermNum := -1;
   Found := False;
   for i := 0 to Term.Count - 1  do
      if ( Term [ i ].SN.Contains ( EditQuery.Text ) ) or ( Term [ i ].SN = EditQuery.Text )
      then
         if Found = False
         then begin
            TermNum := i;
            Found := True;
         end
         else TheEnd ('Искомая строка "'+EditQuery.Text+'" есть в серийном номере двух терминалов: '+ Term [ TermNum ].SN+' и '+Term [ i ].SN );
   if not Found
   then TheEnd('Не могу найти терминал по строке поиска "'+EditQuery.Text+'"');
end;

procedure TForm1.GetUpAndRunning(s: string); var w: variant; begin w := CreateOleObject ( 'Wscript.Shell' ); w.Run ( s , 1 , true ); end;

procedure TForm1.GetLogs;
var
   wordie: TArray<string>;
   w: integer;
   s1, s2: string;
begin
   WriteToGlobalLog ('Начат запрос логов');
   s.Clear;
   s.Add ( '@echo off' );
   s.Add ( 'if exist serialNumber.txt del serialNumber.txt');
   s.Add ( 'if exist *.xz del *.xz');
   s.Add ( 'echo Acquiring logs from ' + Term [ TermNum ].SN );
   s.Add ( 'plink.exe '+config.esppLogin+'@10.184.55.40 -C "./terminals/get_logs_srv.sh '+Term [ TermNum ].IP+ grepString ( Config.dt )+'"' );
   s.Add ( 'pscp.exe ' +config.esppLogin+'@10.184.55.40:./terminals/*.xz ./ ' );
   s.Add ( 'plink.exe '+config.esppLogin+'@10.184.55.40 -C "rm ./terminals/*.xz"' );
   S.Add ( 'if exist 4parser.xz echo Done!' );
   s.Add ( 'if not exist 4parser.xz echo Can''t get logs' );

   wordie := Config.strRun7z.Split(['%'],TStringSplitOptions.ExcludeEmpty);
   s1 := wordie [ 0 ] + '4parser.xz >nul';
   s2 := wordie [ 0 ] + '4parser >nul';
   for w := 1 to Length ( wordie ) - 1 do  begin
      s1 := s1 + wordie [ w ];
      s2 := s2 + wordie [ w ];
   end;
   s.Add ( s1 );
   s.Add ( s2 );

   s.Add ( 'copy net.xz+terminalSerialNumber.csv+receiptsForPrinting.csv serialNumber.txt' );
   s.Add ( 'del net.xz 4parser.xz 4parser terminalSerialNumber.csv receiptsForPrinting.csv' );
   if s.Count > 0 then s.SaveToFile( 'getlogs.bat' );
   GetUpAndRunning( 'getlogs.bat' );
   DeleteFile ( 'getlogs.bat' );
   WriteToGlobalLog ('Завершён запрос логов');
end;

procedure TForm1.CleanUpLogs;
begin
   s.Clear;
   s.Add('@echo off');
   s.Add('mkdir ' + Term [ TermNum ].SN );
   s.Add('move *.txt ' + Term [ TermNum ].SN);
   s.Add('move *.log ' + Term [ TermNum ].SN);
   s.Add('move *.csv ' + Term [ TermNum ].SN);
   s.Add('move *.xz ' + Term [ TermNum ].SN);
   if s.Count > 0 then s.SaveToFile ( 'cleanlogs.bat' );
   GetUpAndRunning ( 'cleanlogs.bat' );
   DeleteFile ( 'cleanlogs.bat' );
end;

procedure TForm1.GetOFD ( doSomething: Boolean );
var
   a, b, t0, t1: TDateTime;
   j, w, NumberOfRuns: integer;
   s: string;
   wordie: TArray<string>;
begin
   if doSomething
   then begin
      WriteToGlobalLog ('Запущен запрос в ОФД');
      a := EncodeDateTime( 1970, 1, 1, 0, 0, 0, 0 ); {начало отсчёта в ОФД}
      b := EncodeDateTime( 2017, 3, 1, 0, 0, 0, 0 ); {начало отсчёта для терминалов}
      t0 := IncDay ( Now , 1 ); {сейчас, плюс сутки запаса}
      j := 0;
      repeat
         t1 := t0;
         t0 := IncWeek ( t1 , Config.ofdPeriod );
         j := j + 1;
         s := Config.strRunOfd;
         wordie := s.Split(['%'],TStringSplitOptions.ExcludeEmpty);
         s := wordie [ 0 ]
            + config.ofdLogin+' '
            + config.ofdPassword+' '
            + Term [ TermNum ].ofdN+' '
            + MilliSecondsBetween( t0, a ).ToString+' '
            + MilliSecondsBetween( t1, a ).ToString;
         for w := 1 to Length ( wordie ) - 1 do  s := s + wordie [ w ];

         for NumberOfRuns := 1 to 20 do  begin
            GetUpAndRunning ( s );
            st.Clear;
            st.LoadFromFile('ofd.csv');
            if st [ 0 ].Length > 100 then Break;
         end;
         st.Clear;

         RenameFile( 'ofd.csv' , 'ofd'+j.ToString+'.csv' );
      until CompareDateTime ( t0 , b ) < 0;
      WriteToGlobalLog ('Сохранён ответ ОФД');
   end;
end;

procedure TForm1.Button1Click(Sender: TObject); begin DoRevise; end;
procedure TForm1.Button2Click(Sender: TObject); begin TheEnd(''); end;

procedure TForm1.DoRevise;
begin
   ClearGlobalLog;
   WriteToGlobalLog   ( 'Начат запрос данных' );
   FindTerminal;
   WriteToGlobalLog   ( 'Терминал определён: '+Term [ TermNum ].SN );
   if DirectoryExists ( Term [ TermNum ].SN )
   then
   else begin
      GetLogs;
      GetESPP ( FileExists ( 'logs.csv' ) );
      GetOFD  ( FileExists ( 'logs.csv' ) );
      WriteToGlobalLog ('Сбор данных завершён');
      if Config.keepGlobalLog then SaveGlobalLog ( 'request.log' );
      CleanUpLogs;
   end;
end;

procedure TForm1.ReadTerm;
var
   wordie: TArray<String>;
   t: TTerm;
   i: integer;
begin
   {читаем конфигурацию}
   ts.Clear;
   if FileExists ( 'kp.conf' )
   then ts.LoadFromFile ( 'kp.conf' )
   else
      if FileExists ( 'kiosk.conf' )
      then ts.LoadFromFile ( 'kiosk.conf' );
   if ts.Count < 5
   then TheEnd('Неполный файл kp.conf');
   with Config do  begin
      try
         dt       := ts [ 0 ].ToInteger;
      except
         dt       :=         32000;
      end;
      ofdPeriod   := -ts [ 1 ].ToInteger;
      termFile    := ts [ 2 ];
      esppLogin   := ts [ 3 ];
      ofdLogin    := ts [ 4 ];
      ofdPassword := ts [ 5 ];
      if ts.Count > 6
      then begin
         FDConnection1.Params.Clear;
         FDConnection1.Params.Add ('Database=' + ts [ 6 ] );
         FDConnection1.Params.Add ('User_Name=ekm');
         FDConnection1.Params.Add ('Password=ekm');
         FDConnection1.Params.Add ('CharacterSet=UTF8');
         FDConnection1.Params.Add ('OSAuthent=No');
         FDConnection1.Params.Add ('DriverID=Ora');
      end;
      if ts.Count > 7 then keepGlobalLog := UpperCase ( ts [ 7 ] ) = 'DEBUG' else keepGlobalLog := False;
      if ts.Count > 8 then strRunOfd := ts [ 8 ] else strRunOfd := 'ofd.jar %';
      if ts.Count > 9 then strRun7z  := ts [ 9 ] else strRun7z  := '7z.exe e %';

   {читаем список терминалов}
      ts.Clear;
      ts.LoadFromFile ( termFile );
   end;
   for i := 0 to ts.Count - 1 do  begin
      wordie  := ts [ i ].Split([' '],TStringSplitOptions.ExcludeEmpty);
      if Length ( wordie ) < 3 then Continue;
      t.SN    := wordie [ 0 ];
      t.IP    := wordie [ 1 ];
      t.ofdN  := wordie [ 2 ];
      term.Add ( t );
   end;
   ts.Clear;
end;

procedure TForm1.FormCreate(Sender: TObject);
var
        i: Integer;
    LList: TStringDynArray;
   wordie: TArray <String>;
begin
   try
      s    := TStringList.Create;
      ts   := TStringList.Create;
      st   := TStringList.Create;
      Term := TList<TTerm>.Create;
      grepFiles := TStringList.Create;
      dir.CreateDirectory( GetCurrentDir );
      LList := dir.GetFiles( GetCurrentDir );
      ofdCSVfile := '';
      for i := 0 to Length ( LList ) - 1  do  begin
         wordie := LList [ i ].Split(['\'],TStringSplitOptions.ExcludeEmpty);
         if  ( wordie [ Length ( wordie ) - 1 ].EndsWith ('.csv') )
         and ( wordie [ Length ( wordie ) - 1 ].Length > 25 )
         then begin
            ofdCSVfile := wordie [ Length ( wordie ) - 1 ];
            Break;
         end;
      end;

      ReadTerm;

      if FileExists('action.csv') or FileExists('logs.csv')
      then TheEnd('Обнаружены ценные данные. Завершение работы во избежание их потери')
      else
         if ParamCount > 0
         then begin
            EditQuery.Text := ParamStr ( 1 );
            DoRevise;
         end
         else
            for i := 0 to Term.Count - 1 do  begin
               EditQuery.Text := Term [ i ].SN;
               DoRevise;
            end;

      TheEnd('');

   except
      on E: Exception do
         TheEnd ( E.ClassName + ': ' + E.Message );
   end;
end;

end.
